# -*- CMake -*- file for tests of classes computing or modifying forces

find_package(YAML)
if(NOT YAML_FOUND)
  set(YAML_URL "https://pyyaml.org/download/libyaml/yaml-0.2.5.tar.gz" CACHE STRING "URL for libyaml tarball")
  set(YAML_MD5 "bb15429d8fb787e7d3f1c83ae129a999" CACHE STRING "MD5 checksum of libyaml tarball")
  mark_as_advanced(YAML_URL)
  mark_as_advanced(YAML_MD5)

  # download and build a local copy of libyaml
  include(ExternalCMakeProject)
  ExternalCMakeProject(libyaml ${YAML_URL} ${YAML_MD5} yaml . CMakeLists.libyaml)
  add_library(Yaml::Yaml ALIAS yaml)
endif()

function(extract_tags out yaml_file)
  file(STRINGS ${yaml_file} TAGS_LINE REGEX "^tags:")
  string(REPLACE "tags:" "" TAGS_LINE "${TAGS_LINE}")
  string(REGEX MATCHALL "[^, \t\r\n]+" TAGS "${TAGS_LINE}")
  set(${out} "${TAGS}" PARENT_SCOPE)
endfunction()

find_package(Python COMPONENTS Interpreter)
if(Python_EXECUTABLE)
  add_custom_target(check-tests
    ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/check_tests.py
    -s ${LAMMPS_SOURCE_DIR} -t ${CMAKE_CURRENT_SOURCE_DIR}/tests
    COMMENT "Check completeness of force style tests")
endif()

if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
  string(REPLACE "/" "\\\\" TEST_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/tests")
else()
  set(TEST_INPUT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/tests)
endif()
add_library(style_tests STATIC yaml_writer.cpp error_stats.cpp test_config_reader.cpp test_main.cpp)
if(YAML_FOUND)
  target_compile_definitions(style_tests PRIVATE TEST_INPUT_FOLDER=${TEST_INPUT_FOLDER})
else()
  # we always use static linkage with local compiled libyaml
  target_compile_definitions(style_tests PRIVATE TEST_INPUT_FOLDER=${TEST_INPUT_FOLDER} YAML_DECLARE_STATIC)
endif()
target_include_directories(style_tests PRIVATE ${LAMMPS_SOURCE_DIR})
target_link_libraries(style_tests PUBLIC GTest::GMock Yaml::Yaml lammps)

# propagate sanitizer options to test tools
if(ENABLE_SANITIZER AND (NOT (ENABLE_SANITIZER STREQUAL "none")))
  target_compile_options(style_tests PUBLIC -fsanitize=${ENABLE_SANITIZER})
  target_link_options(style_tests PUBLIC -fsanitize=${ENABLE_SANITIZER})
endif()

# unit test for error stats class
add_executable(test_error_stats test_error_stats.cpp)
target_include_directories(test_error_stats PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LAMMPS_SOURCE_DIR})
target_link_libraries(test_error_stats PRIVATE GTest::GMockMain)
add_test(NAME ErrorStats COMMAND test_error_stats)

# pair style tester
add_executable(test_pair_style test_pair_style.cpp)
target_link_libraries(test_pair_style PRIVATE lammps style_tests)
if(FFT_SINGLE)
  target_compile_definitions(test_pair_style PRIVATE -DFFT_SINGLE)
endif()

# prepare environment for testers
if(WIN32)
  set(FORCE_TEST_ENVIRONMENT PYTHONPATH=${TEST_INPUT_FOLDER})
else()
  set(FORCE_TEST_ENVIRONMENT PYTHONPATH=${TEST_INPUT_FOLDER}:${LAMMPS_PYTHON_DIR}:$ENV{PYTHONPATH})
endif()
list(APPEND FORCE_TEST_ENVIRONMENT "PYTHONUNBUFFERED=1")
list(APPEND FORCE_TEST_ENVIRONMENT "PYTHONDONTWRITEBYTECODE=1")
list(APPEND FORCE_TEST_ENVIRONMENT "OMP_PROC_BIND=false")
list(APPEND FORCE_TEST_ENVIRONMENT "OMP_NUM_THREADS=4")
list(APPEND FORCE_TEST_ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}")
get_property(BUILD_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(BUILD_IS_MULTI_CONFIG)
  set(LAMMPS_LIB_PATH ${CMAKE_BINARY_DIR}/$<CONFIG>)
else()
  set(LAMMPS_LIB_PATH ${CMAKE_BINARY_DIR})
endif()
if(APPLE)
  list(APPEND FORCE_TEST_ENVIRONMENT "DYLD_LIBRARY_PATH=${LAMMPS_LIB_PATH}:$ENV{DYLD_LIBRARY_PATH}")
elseif(WIN32)
  list(APPEND FORCE_TEST_ENVIRONMENT "LAMMPSDLLPATH=${LAMMPS_LIB_PATH}")
else()
  list(APPEND FORCE_TEST_ENVIRONMENT "LD_LIBRARY_PATH=${LAMMPS_LIB_PATH}:$ENV{LD_LIBRARY_PATH}")
endif()

# tests for molecular systems and related pair styles
file(GLOB MOL_PAIR_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS ${TEST_INPUT_FOLDER}/mol-pair-*.yaml)
# cannot test MSM with single precision data
if(FFT_SINGLE)
  list(FILTER MOL_PAIR_TESTS EXCLUDE REGEX "msm")
endif()
foreach(TEST ${MOL_PAIR_TESTS})
  string(REGEX REPLACE "^.*mol-pair-(.*)\.yaml" "MolPairStyle:\\1" TNAME ${TEST})
  extract_tags(TEST_TAGS ${TEST})
  list(FIND TEST_TAGS "no${CMAKE_SYSTEM_NAME}" _SKIPME)
  if(_SKIPME GREATER_EQUAL 0)
    continue()
  endif()
  add_test(NAME ${TNAME} COMMAND test_pair_style ${TEST})
  set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
  set_tests_properties(${TNAME} PROPERTIES LABELS "${TEST_TAGS}")
endforeach()

# tests for metal-like atomic systems and related pair styles
file(GLOB ATOMIC_PAIR_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS ${TEST_INPUT_FOLDER}/atomic-pair-*.yaml)
foreach(TEST ${ATOMIC_PAIR_TESTS})
  string(REGEX REPLACE "^.*atomic-pair-(.*)\.yaml" "AtomicPairStyle:\\1" TNAME ${TEST})
  extract_tags(TEST_TAGS ${TEST})
  list(FIND TEST_TAGS "no${CMAKE_SYSTEM_NAME}" _SKIPME)
  if(_SKIPME GREATER_EQUAL 0)
    continue()
  endif()
  add_test(NAME ${TNAME} COMMAND test_pair_style ${TEST})
  set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
  set_tests_properties(${TNAME} PROPERTIES LABELS "${TEST_TAGS}")
endforeach()

# tests for Si-like manybody systems and related pair styles
file(GLOB MANYBODY_PAIR_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS ${TEST_INPUT_FOLDER}/manybody-pair-*.yaml)
foreach(TEST ${MANYBODY_PAIR_TESTS})
  string(REGEX REPLACE "^.*manybody-pair-(.*)\.yaml" "ManybodyPairStyle:\\1" TNAME ${TEST})
  extract_tags(TEST_TAGS ${TEST})
  list(FIND TEST_TAGS "no${CMAKE_SYSTEM_NAME}" _SKIPME)
  if(_SKIPME GREATER_EQUAL 0)
    continue()
  endif()
  add_test(NAME ${TNAME} COMMAND test_pair_style ${TEST})
  set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
  set_tests_properties(${TNAME} PROPERTIES LABELS "${TEST_TAGS}")
endforeach()

# bond style tester
add_executable(test_bond_style test_bond_style.cpp)
target_link_libraries(test_bond_style PRIVATE lammps style_tests)

file(GLOB BOND_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS ${TEST_INPUT_FOLDER}/bond-*.yaml)
foreach(TEST ${BOND_TESTS})
  string(REGEX REPLACE "^.*bond-(.*)\.yaml" "BondStyle:\\1" TNAME ${TEST})
  extract_tags(TEST_TAGS ${TEST})
  list(FIND TEST_TAGS "no${CMAKE_SYSTEM_NAME}" _SKIPME)
  if(_SKIPME GREATER_EQUAL 0)
    continue()
  endif()
  add_test(NAME ${TNAME} COMMAND test_bond_style ${TEST})
  set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
  set_tests_properties(${TNAME} PROPERTIES LABELS "${TEST_TAGS}")
endforeach()

# angle style tester
add_executable(test_angle_style test_angle_style.cpp)
target_link_libraries(test_angle_style PRIVATE lammps style_tests)

file(GLOB ANGLE_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS ${TEST_INPUT_FOLDER}/angle-*.yaml)
foreach(TEST ${ANGLE_TESTS})
  string(REGEX REPLACE "^.*angle-(.*)\.yaml" "AngleStyle:\\1" TNAME ${TEST})
  extract_tags(TEST_TAGS ${TEST})
  list(FIND TEST_TAGS "no${CMAKE_SYSTEM_NAME}" _SKIPME)
  if(_SKIPME GREATER_EQUAL 0)
    continue()
  endif()
  add_test(NAME ${TNAME} COMMAND test_angle_style ${TEST})
  set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
  set_tests_properties(${TNAME} PROPERTIES LABELS "${TEST_TAGS}")
endforeach()

# kspace style tester, currently uses the pair style tool
file(GLOB KSPACE_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS ${TEST_INPUT_FOLDER}/kspace-*.yaml)
# cannot test MSM with single precision data
if(FFT_SINGLE)
  list(FILTER KSPACE_TESTS EXCLUDE REGEX "msm")
endif()
foreach(TEST ${KSPACE_TESTS})
  string(REGEX REPLACE "^.*kspace-(.*)\.yaml" "KSpaceStyle:\\1" TNAME ${TEST})
  extract_tags(TEST_TAGS ${TEST})
  list(FIND TEST_TAGS "no${CMAKE_SYSTEM_NAME}" _SKIPME)
  if(_SKIPME GREATER_EQUAL 0)
    continue()
  endif()
  add_test(NAME ${TNAME} COMMAND test_pair_style ${TEST})
  set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
  set_tests_properties(${TNAME} PROPERTIES LABELS "${TEST_TAGS}")
endforeach()

# tester for timestepping fixes
add_executable(test_fix_timestep test_fix_timestep.cpp)
if(NOT BUILD_SHARED_LIBS)
  target_compile_definitions(test_fix_timestep PRIVATE USING_STATIC_LIBS=1)
endif()
if(FFT_SINGLE)
  target_compile_definitions(test_fix_timestep PRIVATE -DFFT_SINGLE)
endif()
target_link_libraries(test_fix_timestep PRIVATE lammps style_tests)

# tests for timestep related fixes (time integration, thermostat, force manipulation, constraints/restraints)
file(GLOB FIX_TIMESTEP_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS ${TEST_INPUT_FOLDER}/fix-timestep-*.yaml)
foreach(TEST ${FIX_TIMESTEP_TESTS})
  string(REGEX REPLACE "^.*fix-timestep-(.*)\.yaml" "FixTimestep:\\1" TNAME ${TEST})
  extract_tags(TEST_TAGS ${TEST})
  list(FIND TEST_TAGS "no${CMAKE_SYSTEM_NAME}" _SKIPME)
  if(_SKIPME GREATER_EQUAL 0)
    continue()
  endif()
  add_test(NAME ${TNAME} COMMAND test_fix_timestep ${TEST})
  set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
  set_tests_properties(${TNAME} PROPERTIES LABELS "${TEST_TAGS}")
endforeach()

# dihedral style tester
add_executable(test_dihedral_style test_dihedral_style.cpp)
target_link_libraries(test_dihedral_style PRIVATE lammps style_tests)

file(GLOB DIHEDRAL_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS ${TEST_INPUT_FOLDER}/dihedral-*.yaml)
foreach(TEST ${DIHEDRAL_TESTS})
  string(REGEX REPLACE "^.*dihedral-(.*)\.yaml" "DihedralStyle:\\1" TNAME ${TEST})
  extract_tags(TEST_TAGS ${TEST})
  list(FIND TEST_TAGS "no${CMAKE_SYSTEM_NAME}" _SKIPME)
  if(_SKIPME GREATER_EQUAL 0)
    continue()
  endif()
  add_test(NAME ${TNAME} COMMAND test_dihedral_style ${TEST})
  set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
  set_tests_properties(${TNAME} PROPERTIES LABELS "${TEST_TAGS}")
endforeach()

# improper style tester
add_executable(test_improper_style test_improper_style.cpp)
target_link_libraries(test_improper_style PRIVATE lammps style_tests)

file(GLOB IMPROPER_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS ${TEST_INPUT_FOLDER}/improper-*.yaml)
foreach(TEST ${IMPROPER_TESTS})
  string(REGEX REPLACE "^.*improper-(.*)\.yaml" "ImproperStyle:\\1" TNAME ${TEST})
  extract_tags(TEST_TAGS ${TEST})
  list(FIND TEST_TAGS "no${CMAKE_SYSTEM_NAME}" _SKIPME)
  if(_SKIPME GREATER_EQUAL 0)
    continue()
  endif()
  add_test(NAME ${TNAME} COMMAND test_improper_style ${TEST})
  set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
  set_tests_properties(${TNAME} PROPERTIES LABELS "${TEST_TAGS}")
endforeach()

if(MLIAP_ENABLE_PYTHON AND (NOT WIN32))
  add_executable(test_mliappy_unified test_mliappy_unified.cpp)
  target_link_libraries(test_mliappy_unified PRIVATE lammps GTest::GMockMain)
  add_test(NAME TestMliapPyUnified COMMAND test_mliappy_unified)
  set_tests_properties(TestMliapPyUnified PROPERTIES ENVIRONMENT "${FORCE_TEST_ENVIRONMENT}")
endif()

add_executable(test_pair_list test_pair_list.cpp)
target_link_libraries(test_pair_list PRIVATE lammps GTest::GMockMain)
add_test(NAME TestPairList COMMAND test_pair_list)

